﻿using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Services;
using Microsoft.ApplicationServer.Caching;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using CacheHandlerManager;

/// <summary>
/// Summary description for ASNet
/// </summary>
[WebService(Namespace = "ASNet")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
// [System.Web.Script.Services.ScriptService]
public class ASNet : System.Web.Services.WebService {
    private XmlDocument xConf;
    private string hostName;
    private int cachePort;
    private string cacheHealthName;
    private string cacheSessionsName;
    private CacheHandler chSessions;
    private CacheHandler chHealth;
    private int lockTimeout;

    public ASNet () {
        //Uncomment the following line if using designed components 
        //InitializeComponent();

        //initialise all vars from the config file
        xConf = new XmlDocument();
        xConf.Load("C:\\ASNet\\local_conf.xml");
        hostName = xConf.GetElementsByTagName("localHostName").Item(0).InnerText;
        cachePort = Int32.Parse(xConf.GetElementsByTagName("localHostCachePort").Item(0).InnerText);
        cacheHealthName = xConf.GetElementsByTagName("healthPartition").Item(0).InnerText;
        cacheSessionsName = xConf.GetElementsByTagName("sessionPartition").Item(0).InnerText;
        lockTimeout = Int32.Parse(xConf.GetElementsByTagName("lockTimeout").Item(0).InnerText);

        chSessions = new CacheHandler(hostName, cachePort, cacheSessionsName);
        chHealth = new CacheHandler(hostName, cachePort, cacheHealthName);
    }

    //this gets called by the HTTP module which intercepts and redirects the requests
    public string getURL(string IPAddress, string userName, string key, string parameters)
    {
        string concat = IPAddress + userName + key + parameters;
        byte[] tmpSrc, tmpHash;
        tmpSrc = ASCIIEncoding.ASCII.GetBytes(concat);
        //using SHA256 hash to store in the cache as a handle
        tmpHash = SHA256.Create().ComputeHash(tmpSrc);
        string sessionID = ASCIIEncoding.ASCII.GetString(tmpHash);

        return getSessionURL(sessionID);
    }

    //if no params, bypass the sessions cache and just return the next available url
    public string getURL()
    {
        return getServerURL();
    }

    //returns the session handle if it exits in the cache, otherwise first adds it to the cache
    public string getSessionURL(string sessionID)
    {
        //the return URL to the HTTP pump
        string URL = null;

        URL = (string)chSessions.getWithNoLock(sessionID);

        //add a new session to the cache if it doesn't exist there
        if (URL == null)
        {
            URL = getServerURL();
            chSessions.putWithNoLock(sessionID, URL);
        }

        return URL;
    }

    //here we pull the URL for the best pump
    private string getServerURL()
    {
        LinkedList<KeyValuePair<string, string>> freeList = (LinkedList<KeyValuePair<string, string>>)chHealth.getWithLock("freeList", lockTimeout);
        LinkedList<KeyValuePair<string, string>> busyList = (LinkedList<KeyValuePair<string, string>>)chHealth.getWithLock("busyList", lockTimeout);

        //if no hosts in either the free of busy caches, throw an exception
        if (freeList.Count == 0)
        {
            if (busyList.Count == 0)
            {
                chHealth.unlock("freeList");
                throw (new Exception("No cache hosts available"));
            }
            //pull the first host from the busy cache if there's nothing in the free one; then move it to the back of the list - round robin
            else
            {
                KeyValuePair<string, string> kvp = (KeyValuePair<string, string>) busyList.First.Value;
                if (busyList.Count > 1)
                {
                    busyList.Remove(kvp);
                    busyList.AddLast(kvp);
                    chHealth.putWithUnlock("busyList", busyList);
                    chHealth.unlock("freeList");
                }
                return kvp.Value;
            }
        }
        //get the first pump from the free list if available and push it back to the end of the list - round robin
        else
        {
            KeyValuePair<string, string> kvp = (KeyValuePair<string, string>)freeList.First.Value;
            if (freeList.Count > 1)
            {
                freeList.Remove(kvp);
                freeList.AddLast(kvp);
                chHealth.putWithUnlock("freeList", freeList);
                chHealth.unlock("busyList");
            }
            return kvp.Value;
        }
    }
}
